package com.device.service.impl;

import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.device.domain.CabinetSlot;
import com.device.domain.PowerBank;
import com.device.domain.Station;
import com.device.domain.StationLocation;
import com.device.domain.entity.Cabinet;
import com.device.domain.vo.AvailableProwerBankVo;
import com.device.domain.vo.ScanChargeVo;
import com.device.domain.vo.StationVo;
import com.device.emqx.EmqxClientWrapper;
import com.device.emqx.constant.EmqxConstants;
import com.device.mapper.CabinetSlotMapper;
import com.device.mapper.PowerBankMapper;
import com.device.service.DeviceService;
import com.device.service.ICabinetService;
import com.device.service.IMapService;
import com.device.service.IStationService;
import com.share.api.order.api.RemoteOrderInfoService;
import com.share.api.order.domain.OrderInfo;
import com.share.api.rule.api.RemoteFeeRuleService;
import com.share.api.rule.domain.FeeRule;
import com.share.common.core.constant.MessageConstant;
import com.share.common.core.constant.SecurityConstants;
import com.share.common.core.context.SecurityContextHolder;
import com.share.common.core.domain.R;
import com.share.common.core.exception.ServiceException;
import com.share.common.core.utils.StringUtils;
import com.share.common.core.utils.bean.BeanUtils;
import com.share.rabbit.constant.MqConst;
import com.share.rabbit.service.RabbitService;
import com.share.user.api.RemoteUserService;
import com.share.user.domain.UserInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Metrics;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class DeviceServiceimpl implements DeviceService {
    @Autowired
    private MongoTemplate  mongoTemplate;
    @Autowired
    private IStationService stationService;
    @Autowired
    private ICabinetService cabinetService;
    @Autowired
    private IMapService mapService;
    @Autowired
    private RemoteFeeRuleService remoteFeeRuleService;
    @Autowired
    RemoteUserService remoteUserService;
    @Autowired
    RemoteOrderInfoService remoteOrderInfoService;
    @Autowired
    EmqxClientWrapper emqxClientWrapper;
    @Autowired
    CabinetSlotMapper cabinetSlotMapper;
    @Autowired
    PowerBankMapper powerBankMapper;
    @Autowired
    RabbitService rabbitService;



    @Override
    //获取附近站点信息列表
    public List<StationVo> nearbyStationList(String longitude, String latitude) {

        //坐标，确定中心点
        //这个对象里面我传了一个精度和一个纬度

        GeoJsonPoint geoJsonPoint = new GeoJsonPoint(Double.parseDouble(longitude), Double.parseDouble(latitude));
        //设置查询半径
        Distance distance = new Distance(500000, Metrics.KILOMETERS);

        //画圆
        //中心位置和半径
        Circle circle = new Circle(geoJsonPoint, distance);

        //查询mongoDB数据
        Query query = Query.query(Criteria.where("location").withinSphere(circle));
        List<StationLocation> stationLocations = mongoTemplate.find(query, StationLocation.class);
        System.out.println("MongoDB中查询到的数据为："+stationLocations);

        //查询其他需要数据，进行封装
        //从stationLocations中首先获取到这个站点的id
        List<Long> stationIdList = stationLocations.stream().map(StationLocation::getStationId).collect(Collectors.toList());

        //检查stationId这个里面有没有数据，如果没有数据，则返回null，避免不必要的sql
        if (stationIdList.isEmpty()){
            log.warn("MongoDB数据库里面没有数据，直接返回空的列表");
            return Collections.emptyList();
        }

        //然后根据id查询对应站点的数据
        System.out.println("获取的id信息为："+stationIdList);
       LambdaQueryWrapper<Station> wrapper = new LambdaQueryWrapper();
        wrapper.in(Station::getId,stationIdList);
        List<Station> stationlist = stationService.list(wrapper);
        System.out.println("我查询到的数据为："+stationlist);

        //将Station类型的数据拷贝到StationVo类型中
        List<StationVo> StationvoList = new ArrayList();
        //遍历
        stationlist.forEach(station -> {
            StationVo stationVo = new StationVo();
            //拷贝
            BeanUtils.copyProperties(station,stationVo);

            System.out.println("我拷贝的数据为："+stationVo);

            //计算距离
            Double distanceStation = mapService.calculateDistance(longitude, latitude,
                    station.getLongitude().toString(),
                    station.getLatitude().toString());

            stationVo.setDistance(distanceStation.toString());

            //获取柜机信息

            Long cabinetId = station.getCabinetId();//首先在station这个类中获取到这个柜机的id
            //然后根据这个id查询到这个柜机的空闲充电宝数量
            LambdaQueryWrapper<Cabinet> cabinetwrapper = new LambdaQueryWrapper();
            cabinetwrapper.eq(Cabinet::getId,cabinetId);
            Cabinet cabinet = cabinetService.getById(cabinetId);//这里我是根据id查询到这个柜机的信息
            //if判断这个充电宝数量是否大于0
            if (cabinet.getAvailableNum()>0){
                stationVo.setIsUsable("1");
            }
            else {
                stationVo.setIsUsable("0");
            }
            //if判断这个归还充电宝的插槽是否大于0
            if (cabinet.getFreeSlots()>0){
                stationVo.setIsReturn("1");
            }
            else {
                stationVo.setIsReturn("0");
            }

            //获取站点规则数据
            Long feeRuleId = station.getFeeRuleId();
            //远程调用获取规则数据
            R<FeeRule> feeRule = remoteFeeRuleService.getFeeRule(feeRuleId);
            FeeRule data = feeRule.getData();
            String description = data.getDescription();
            stationVo.setFeeRule(description);
            StationvoList.add(stationVo);
        });
        return StationvoList;
    }
    /*
     * 下面这段代码是为了做门店的详情数据
     * */
    @Override
    public StationVo getStation(Long id, String latitude, String longitude) {
        //根据门店id获取门店信息
        Station station = stationService.getById(id);
        //把数据都封装到stationVo中
        StationVo stationVo = new StationVo();
        BeanUtils.copyProperties(station,stationVo);

        //设置门店距离
        Double distance = mapService.calculateDistance(
                longitude, latitude,
                station.getLongitude().toString(),
                station.getLatitude().toString());
        //将查询出来的距离赋给stationVo
        stationVo.setDistance(distance.toString());
        //判断柜机信息是否可用
        //获取柜机的信息
        Cabinet cabinet = cabinetService.getById(station.getCabinetId());
        //判断这个柜机的充电宝数量是否大于0，可借用
        if (cabinet.getAvailableNum() > 0){
            stationVo.setIsUsable("1");
        }else {
            stationVo.setIsUsable("0");
        }
        //判断这个空闲插槽位置是否大于0，可归还
        if (cabinet.getFreeSlots() > 0){
            stationVo.setIsReturn("1");
        }else {
            stationVo.setIsReturn("0");
        }

        //下面这段代码是为了计算费用
        FeeRule feeRule = remoteFeeRuleService.getFeeRule(station.getFeeRuleId()).getData();
        stationVo.setFeeRule(feeRule.getDescription());
        return stationVo;
    }

    /**
     * 扫码充电
     */
    @Override
    public ScanChargeVo scanCharge(String cabinetNo) {
        // 扫码充电返回对象
        ScanChargeVo scanChargeVo = new ScanChargeVo();
        try {
            // 1 远程调用：根据当前登录用户id查询用户信息，
            // 从用户信息获取是否支持免押金充电
            R<UserInfo> info = remoteUserService.getInfo(SecurityContextHolder.getUserId());
            UserInfo userInfo = info.getData();
            // 判断
            if (userInfo == null) {
                throw new ServiceException(MessageConstant.FAIL_GET_USER_INFO);
            }
            // 判断是否支持免押金
            if ("0".equals(userInfo.getDepositStatus())) {
                throw new ServiceException(MessageConstant.NOT_APY_FREE_DEPOSIT);
            }
            // 可以做让用户交押金业务代码开发.....

            // 2 远程调用：判断用户是否有未完成订单
            R<OrderInfo> noFinishOrder = remoteOrderInfoService.getNoFinishOrder(SecurityContextHolder.getUserId());
            OrderInfo orderInfo = noFinishOrder.getData();
            // 判断是否有未完成订单
            if (orderInfo != null) {
                // 有未完成订单
                String status = orderInfo.getStatus();
                if ("0".equals(status)) {
                    scanChargeVo.setStatus("2");
                    scanChargeVo.setMessage(MessageConstant.THERE_IS_NO_RETURN_POWER_BANK);
                    return scanChargeVo;
                }
                if ("1".equals(status)) {
                    scanChargeVo.setStatus("3");
                    scanChargeVo.setMessage(MessageConstant.THERE_ARE_UNPAID_ORDERS);
                    return scanChargeVo;
                }
            }

            // 3 从柜机里面获取最优充电宝
            AvailableProwerBankVo availableProwerBankVo = this.checkAvailableProwerBank(cabinetNo);
            // 判断
            if (availableProwerBankVo == null) {
                throw new ServiceException(MessageConstant.NO_AVAILABLE_POWER_BANK);
            }
            // 如果错误提示信息不为空，则抛出ServiceException异常
            if (!StringUtils.isEmpty(availableProwerBankVo.getErrMessage())) {
                throw new ServiceException(availableProwerBankVo.getErrMessage());
            }

            // 4 把选择最优充电宝弹出
            // 使用MQTT弹出充电宝
            // (1) 创建一个JSONObject实例用于存储消息信息
            JSONObject object = new JSONObject();
            // (2) 将当前用户ID存入JSONObject中，键名为"uId"
            object.put("uId", SecurityContextHolder.getUserId());
            // (3) 生成一个格式为"mm"后跟8位随机字符的字符串，并存入JSONObject中，键名为"mNo"
            object.put("mNo", "mm" + RandomUtil.randomString(8));
            // (4) 将柜子编号存入JSONObject中，键名为"cNo"
            object.put("cNo", cabinetNo);
            // (5) 将可用电源银行编号存入JSONObject中，键名为"pNo"
            object.put("pNo", availableProwerBankVo.getPowerBankNo());
            // (6) 将插槽编号存入JSONObject中，键名为"sNo"
            object.put("sNo", availableProwerBankVo.getSlotNo());
            // (7) 使用EmqxConstants中的常量和柜子编号格式化主题名称
            String topic = String.format(EmqxConstants.TOPIC_SCAN_SUBMIT, cabinetNo);
            // (8) 调用emqxClientWrapper的publish方法，发布主题和JSONObject转换成的字符串消息
            emqxClientWrapper.publish(topic, object.toJSONString());


            // 5 返回封装需要数据
            scanChargeVo.setStatus("1");
            return scanChargeVo;

        } catch (Exception e) {
            // 处理异常
            scanChargeVo.setStatus("0");
            scanChargeVo.setMessage(e.getMessage());
            return scanChargeVo;
        }
    }



    /**
     * 根据柜机编号获取一个可用最优的充电宝
     *
     * @param cabinetNo 柜机编号
     * @return
     */
    private AvailableProwerBankVo checkAvailableProwerBank(String cabinetNo) {
        //1 创建AvailablePowerBankVo对象
        AvailableProwerBankVo availableProwerBankVo = new AvailableProwerBankVo();

        //2 根据cabinetNo柜机编号查询柜机信息
        Cabinet cabinet = cabinetService.getBtCabinetNo(cabinetNo);

        //3 判断柜机中可用充电宝数量是否大于0
        if (cabinet.getAvailableNum() == 0) {
            availableProwerBankVo.setErrMessage(MessageConstant.NO_AVAILABLE_POWER_BANK);
            return availableProwerBankVo;
        }

        //4 根据柜机id查询插槽列表，返回list集合
        List<CabinetSlot> cabinetSlotList = cabinetSlotMapper.selectList(new LambdaQueryWrapper<CabinetSlot>().eq(CabinetSlot::getCabinetId, cabinet.getId()));

        //5 从第四步返回插槽列表list集合 获取对应充电宝id集合
        List<Long> powerBankIdList = cabinetSlotList.stream().filter(item -> null != item.getPowerBankId()).map(CabinetSlot::getPowerBankId).toList();

        //6 根据充电宝id列表查询对应充电宝信息
        List<PowerBank> powerBankList = powerBankMapper.selectList(new LambdaQueryWrapper<PowerBank>().in(PowerBank::getId, powerBankIdList).eq(PowerBank::getStatus, "1"));
        //判断集合是否为空
        if (CollectionUtils.isEmpty(powerBankList)) {
            availableProwerBankVo.setErrMessage(MessageConstant.NO_AVAILABLE_POWER_BANK);
            return availableProwerBankVo;
        }

        //7 把上一步获取充电宝信息集合进行排序（根据电量排序）
        // 如果集合大于1，则进行排序
        if (powerBankList.size() > 1) {
            //根据电量值对充电宝列表进行降序排序
            Collections.sort(powerBankList, ((o1, o2) -> o2.getElectricity().compareTo(o1.getElectricity())));
        }

        //8 获取电量最多的充电宝信息
        PowerBank powerBank = powerBankList.get(0);

        //9 获取电量最多的充电宝对应插槽信息
        CabinetSlot cabinetSlot = cabinetSlotList.stream().filter(item -> null != item.getPowerBankId() && item.getPowerBankId().equals(powerBank.getId())).collect(Collectors.toList()).get(0);

        //10 锁定插槽（更新插槽状态）
        cabinetSlotMapper.updateById(cabinetSlot);

        //11 返回需要vo数据
        availableProwerBankVo.setSlotNo(cabinetSlot.getSlotNo());
        availableProwerBankVo.setPowerBankNo(powerBank.getPowerBankNo());

        // 扫码后未弹出充电宝等情况，延迟解锁
        rabbitService.sendDealyMessage(MqConst.EXCHANGE_DEVICE, MqConst.ROUTING_UNLOCK_SLOT, JSONObject.toJSONString(cabinetSlot), MqConst.CANCEL_UNLOCK_SLOT_DELAY_TIME);
        return availableProwerBankVo;
    }


    /**
     * 解锁插槽
     */
    @Override
    public void unlockSlot(CabinetSlot cs) {
        CabinetSlot cabinetSlot = cabinetSlotMapper.selectById(cs.getId());
        if("2".equals(cabinetSlot.getStatus())) {
            //状态（1：占用 0：空闲 2：锁定）
            cabinetSlot.setStatus("1");
            cabinetSlot.setUpdateTime(new Date());
            cabinetSlotMapper.updateById(cabinetSlot);
        }
    }
}
